package cn.fengLone.pay.service.impl;

import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import cn.fengLone.pay.dao.BillHisDao;
import cn.fengLone.pay.dao.BillHisErrorDao;
import cn.fengLone.pay.dao.PayInfoDao;
import cn.fengLone.pay.dao.PaymentDao;
import cn.fengLone.pay.dao.WeiXinFullDao;
import cn.fengLone.pay.entity.BillHisErrorInfo;
import cn.fengLone.pay.entity.BillHisInfo;
import cn.fengLone.pay.entity.PayInfo;
import cn.fengLone.pay.entity.Payment;
import cn.fengLone.pay.entity.WeiXinFull;
import cn.fengLone.pay.exception.BaseException;
import cn.fengLone.pay.service.WeiXinNotifyService;
import cn.fengLone.pay.util.HttpsUtil;
import cn.fengLone.pay.util.SimpleUtil;
import cn.fengLone.pay.util.SpringUtil;
import cn.fengLone.pay.util.wechat.config.MyConfig;

import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayUtil;

@Service("WeiXinNotifyService")
@Transactional(rollbackFor=Exception.class)
public class WeiXinNotifyServiceImpl implements WeiXinNotifyService {

	private Logger logger = Logger.getLogger(WeiXinNotifyService.class);
	@Resource
	private PayInfoDao payInfoDao;

	@Resource
	private WeiXinFullDao weiXinFullDao;

	@Resource
	private BillHisDao billHisDao;

	@Resource
	private PaymentDao paymentDao;

	@Resource
	private BillHisErrorDao billHisErrorDao;

	/**
	 * 订单支付结果通知
	 * 
	 * 推荐的做法是，当收到通知进行处理时， 1、首先检查对应业务数据的状态，判断该通知是否已经处理过 如果没有处理过再进行处理，
	 * 如果处理过直接返回结果成功。 2、在对业务数据进行状态检查和处理之前，要采用数据锁进行并发控制，以避免函数重入造成的数据混乱。
	 * 特别提醒：商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额
	 * 是否与商户侧的订单金额一致，防止数据泄漏导致出现“假通知”，造成资金损失。
	 * 
	 * 
	 * 1、数据并发控制 2、签名验证 3、校验订单金额是否一致 4、检查业务数据状态是否已经处理过 5、异步通知第三方系统
	 * 
	 * https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7
	 * 
	 * @param xmlData
	 *            微信支付平台返回的支付结果
	 * @return
	 * @throws Exception
	 */
	public String wechatNotify(String xmlData) throws Exception{
		Map<String, String> resultMap = new HashMap<String, String>();
		if(xmlData == null || xmlData.equals("")){
			resultMap.put("return_code", "<![CDATA[FAIL]]>");
			resultMap.put("return_msg", "<![CDATA[返回的参数为空值]]");
			return WXPayUtil.mapToXml(resultMap);
		}
		MyConfig config = null;
		Map<String, String> map = null;
		try {
			map = WXPayUtil.xmlToMap(xmlData); // 转换成map
			String out_trade_no = map.get("out_trade_no"); // 商家订单号
			// 商户系统内部订单号，要求32个字符内，只能是数字、大小写字母_-|*@
			// ，且在同一个商户号下唯一。
			// 根据商家订单号查找系统订单判断金额是否正确，防止出现假订单
			// 根据订单号获取应用内部订单，判断获取到的订单状态，如已经处理，则直接返回SUCESS，否则修改订单状态，返回SUCESS

			// 3、根据订单号查询系统订单，判断订单是不是 存在，符合时，将会认为订单正确
			// 查询订单状态
			PayInfo payInfo = payInfoDao.queryPayInfoByTradeNum(out_trade_no);
			if (payInfo == null) {
				//没有找到订单
				resultMap.put("return_code", "<![CDATA[FAIL]]>");
				resultMap.put("return_msg", "<![CDATA[没有找到订单\t"+out_trade_no+"!]]");
				return WXPayUtil.mapToXml(resultMap);
			}
			// 判斷该订单是否已经处理过
			if (SimpleUtil.orderIsSucces(payInfo.getPayResult())) {
				// 已经处理过该订单状态，不需要再次处理
				resultMap.put("return_code", "<![CDATA[SUCCESS]]>");
				resultMap.put("return_msg", "<![CDATA[OK]]");
				return WXPayUtil.mapToXml(resultMap);
			}

			// 获取网站发起的支付方式是否已经接入
			Payment payment = paymentDao.queryPaymentBySiteIdAndPayType(
					payInfo.getWebSite().getId(), payInfo.getPayType());
			if (payment == null) {
				resultMap.put("return_code", "<![CDATA[FAIL]]>");
				resultMap.put("return_msg", "<![CDATA["+SpringUtil.getI18nMsg("no_pay_type")+"]]");
				return WXPayUtil.mapToXml(resultMap);
			}
			String mch_id = map.get("mch_id");// 获取商户号，从数据库中查询商户
			
			if (payment.getIsFullInfo() == '1') {
				// 非营业执照接入，启动代理收款模式
				// ///加载本平台通过营业执照申请到的公钥私钥信息
				config = new MyConfig(SimpleUtil.wechatPayMap.get("appID"),
						SimpleUtil.wechatPayMap.get("mchID"),
						SimpleUtil.wechatPayMap.get("mchKey"));
			}else{
				WeiXinFull weiXinFull = weiXinFullDao
						.queryWeiXinFullByMchId(mch_id);
				if (weiXinFull == null) {
					// 没有找到接入的商户
					resultMap.put("return_code", "<![CDATA[FAIL]]>");
					resultMap.put("return_msg", "<![CDATA[没有找到该商户!]]");
					return WXPayUtil.mapToXml(resultMap);
				}
				config = new MyConfig(weiXinFull.getAppID(),
						weiXinFull.getMchID(), weiXinFull.getMchKey());
				
				
			}
			
			if (payment.getIsFullInfo() == '1') {
				// 非营业执照接入，启动代理收款模式
				// ///加载本平台通过营业执照申请到的公钥私钥信息
				config = new MyConfig(SimpleUtil.wechatPayMap.get("appID"),
						SimpleUtil.wechatPayMap.get("mchID"),
						SimpleUtil.wechatPayMap.get("mchKey"));
			}else{
				WeiXinFull weiXinFull = weiXinFullDao
						.queryWeiXinFullByMchId(mch_id);
				if (weiXinFull == null) {
					// 没有找到接入的商户
					resultMap.put("return_code", "<![CDATA[FAIL]]>");
					resultMap.put("return_msg", "<![CDATA[没有找到该商户!]]");
					return WXPayUtil.mapToXml(resultMap);
				}
				config = new MyConfig(weiXinFull.getAppID(),
						weiXinFull.getMchID(), weiXinFull.getMchKey());
				
			}

			WXPay wxpay = new WXPay(config);
			// 对微信返回的结果进行验签
			if (!wxpay.isPayResultNotifySignatureValid(map)) {
				// 签名错误，如果数据里没有sign字段，也认为是签名错误
				resultMap.put("return_code", "<![CDATA[FAIL]]>");
				resultMap.put("return_msg", "<![CDATA[验签错误!]]");
				return WXPayUtil.mapToXml(resultMap);
			}

			// 签名正确
			//注意特殊情况：订单已经退款，但收到了支付结果成功的通知，不应把商户侧订单状态从退款改成支付成功
			String total_fee = map.get("total_fee"); // 总金额..
			String payTotal_fee = payInfo.getTotal_fee();// 支付金额
			String payResult = map.get("result_code"); // 微信支付业务处理结果
			String queryId = map.get("transaction_id");  //微信系统内部订单号码
				
			if (payResult.trim().equalsIgnoreCase("FAIL")
					|| !payTotal_fee.trim().equals(total_fee.trim())) {
				payInfo.setPayResult('3');// 2支付成功，3支付失败
				payInfo.setPaymsg(map.get("err_code") + ":"
						+ map.get("err_code_des"));
				logger.info("订单状态... 03");
			} else {
				payInfo.setPayResult('2');// 2支付成功，3支付失败
				payInfo.setPaymsg("成功");
				logger.info("订单状态... 02");
			}
				
			payInfo.setQueryId(queryId);
			//通知第三方系统，返回给支付宝处理结果
			Integer sendResultInteger = HttpsUtil.sendMsgToClient(payInfo.getMertradeNum(), payInfo.getTotal_fee(), payInfo.getPayResult(), payInfo.getWebSite().getSiteToken(), payInfo.getWebSite().getPublicKey(), payInfo.getWebSite().getSiteUrl());
			payInfo.setNotifyResult(sendResultInteger);
			payInfoDao.updatePayResult(payInfo);

			// 保存对账信息
			BillHisInfo billHisInfo = billHisDao
					.queryBillByTradeNum(out_trade_no);
			if (billHisInfo != null) {
				//更新账单表
				String time_end  = map.get("time_end"); //20141030133525
				
				SimpleDateFormat format = new SimpleDateFormat("YYYYMMddHHmmss");
				Timestamp send_pay_date = new Timestamp(format.parse(time_end).getTime());
				
				billHisInfo.setResultTime(send_pay_date);  //本次交易打款给卖家的时间
				billHisInfo.setBillResult(payInfo.getPayResult()); //支付结果
				billHisInfo
						.setBillMsg("异步通知结果");
				
				billHisDao.updateBillHis(billHisInfo);
				
				// 已经保存了这个对账信息，不需要保存
				resultMap.put("return_code", "<![CDATA[SUCCESS]]>");
				resultMap.put("return_msg", "<![CDATA[OK]]");
				return WXPayUtil.mapToXml(resultMap);
			}else{
				billHisInfo =  new BillHisInfo();
				billHisInfo.setPayId(payInfo.getId());
					billHisInfo.setPayType(payInfo.getPayType());
					billHisInfo.setSiteId(payInfo.getWebSite().getId());
					billHisInfo.setTradeNum(out_trade_no);
					billHisInfo.setBillType('2');// 异步通知
					billHisInfo.setBillResult(map.get("result_code").equals(
							"SUCCESS") ? '2' : '3');
					billHisInfo
							.setBillMsg(map.get("result_code").equals("SUCCESS") ? map
									.get("err_code")
									+ "-------"
									+ map.get("err_code_des") : "");
					billHisInfo.setCreateTime(new Timestamp(new Date().getTime()));
					billHisInfo.setResultTime(new Timestamp(Long.parseLong(map
							.get("time_end"))));

					billHisDao.addBillHis(billHisInfo);
			}
			if(sendResultInteger == 1){
				// 通知过程中出现异常，记录通知过程，等待下次通知
				BillHisErrorInfo billHisErrorInfo = null;
				
				billHisErrorInfo = billHisErrorDao.queryBillHisErrorInfo(out_trade_no,payInfo.getWebSite().getId());
				if(billHisErrorInfo == null){
					//保存
					billHisErrorInfo = new BillHisErrorInfo();

					billHisErrorInfo.setPayId(payInfo.getId());
					billHisErrorInfo.setPayType(payInfo.getPayType());
					billHisErrorInfo.setSiteId(payInfo.getWebSite().getId());
					billHisErrorInfo.setTradeNum(out_trade_no);
					billHisErrorInfo.setBillType('2');// 异步通知
					billHisErrorInfo.setBillResult(map.get("result_code")
							.equals("SUCCESS") ? '2' : '3');
					billHisErrorInfo.setBillMsg("通知第三方系统出错!");
					billHisErrorInfo.setCreateTime(new Timestamp(new Date()
							.getTime()));
					String time_end  = map.get("time_end"); //20141030133525
					
					SimpleDateFormat format = new SimpleDateFormat("YYYYMMddHHmmss");
					Timestamp send_pay_date = new Timestamp(format.parse(time_end).getTime());
					
					billHisErrorInfo.setResultTime(send_pay_date);
					billHisErrorInfo.setErrorMsg("通知第三方系统出错!");
					billHisErrorInfo.setNotifyNum(1);
					billHisErrorDao.addBillHis(billHisErrorInfo);
				}else {
					//更新
					billHisErrorInfo.setNotifyNum(billHisErrorInfo.getNotifyNum()+1);;
					billHisErrorInfo.setBillMsg("通知第三方系统出错!");
					billHisErrorInfo.setErrorMsg("通知第三方系统出错!");
					billHisErrorInfo.setBillType('2');// 异步通知
					billHisErrorInfo.setBillResult(map.get("result_code")
							.equals("SUCCESS") ? '2' : '3');
					billHisErrorDao.updateBillHis(billHisErrorInfo);
				}
				
			}
			resultMap.put("return_code", "<![CDATA[SUCCESS]]>");
			resultMap.put("return_msg", "<![CDATA[OK]]");
			return WXPayUtil.mapToXml(resultMap);
		} catch (Exception e) {
			throw new BaseException(e,false,"<![CDATA[初始化微信SDK失败!]]");
		}
	}

}
